package lab5task1;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 
 * @author Bulaev Igor
 *
 * Class that calculates progression sum in given number of threads
 *  using ExecutorService and Callable from java.util.concurrent library
 * 
 * @param sum - progression sum
 * @param threadCount - number of threads to be used
 * @param start - start value of progression
 * @param end - end value of progression
 *
 */

public class SinSumConcurrentLib {		

	/**
	 * 
	 * Method that calculates progression sum
	 * 
	 * @param threadRange - range of values calculated in single thread
	 * 
	 */
	
	public double calculateSum(int threadCount, int start, int end) {
		if(start > end) {
			throw new IllegalArgumentException("start > end");
		}
		if(threadCount > Math.abs(start) + Math.abs(end)) {
			throw new IllegalArgumentException("Number of threads > number of values in progression");
		}	
		
		/** Get range of values for single thread by dividing sum of values by number of threads */
		
		int threadRange = ((Math.abs(start) + Math.abs(end)) / threadCount);
		ExecutorService service = Executors.newFixedThreadPool(threadCount);	
		List<Callable<Double>> tasks = new ArrayList<>();
		List<Future<Double>> resultList = new ArrayList<>();
		
		/** Create tasks for calculation */
		
		for (int i = 0; i < threadCount; i++) {
			int startPos = start + i * threadRange;
			if (i != threadCount - 1)
				tasks.add(new WorkerCallable(startPos, startPos + threadRange - 1));
			else
				tasks.add(new WorkerCallable(startPos, end));			
		}
		
		/** Wait for end of tasks and sum them up */
		
		double sum = 0d;
		try {
			resultList = service.invokeAll(tasks);
			for(Future<Double> curResult : resultList) {
				sum += curResult.get();
			}
		} catch(InterruptedException ie) {
			ie.printStackTrace();
		} catch(ExecutionException ee) {
			ee.printStackTrace();
		}		
		service.shutdown();
		sum = (double)Math.round(sum * 1000000) / 1000000;
		return sum;
	}

	/**
	 * 
	 * Worker callable
	 * 
	 * @param fromRange - lowest value in thread's range
	 * @param toRange - highest value in thread's range
	 *
	 */
	
	private class WorkerCallable implements Callable<Double> {
	
		private final int fromRange;
		private final int toRange;
		
		public WorkerCallable(int fromRange, int toRange) {
			this.fromRange = fromRange;
			this.toRange = toRange;
		}
		
		@Override
		public Double call() throws Exception {
			double sum = 0;
			for (int i = fromRange; i <= toRange; i++) {					
				sum += Math.sin(i);				
			}			
			return sum;
		}

	}
}
